package asteroids.model.programs.statements;

import java.util.List;
import java.util.Set;

import asteroids.model.GameObject;
import asteroids.model.Ship;
import asteroids.model.programs.Program;
import asteroids.model.programs.expressions.EntityLiteral;
import asteroids.model.programs.expressions.ExprBoolEvaluator;
import asteroids.model.programs.expressions.ExprDoubleEvaluator;
import asteroids.model.programs.expressions.ExprEvaluator;
import asteroids.model.programs.expressions.ExprStringEvaluator;
import asteroids.model.programs.expressions.Expression;

public class StatementRunner {

	public StatementRunner(Ship ship, Program program) {
		this.ship = ship;
		this.program = program;
		ede = new ExprDoubleEvaluator(ship);
		ebe  = new ExprBoolEvaluator(ship);
		ese = new ExprStringEvaluator(ship);
	}
	
 	public Ship getShip() {
		return ship;
	}

	private final Ship ship;
	
	public Program getProgram() {
		return program;
	}
	
	private final Program program;
	
	private final ExprEvaluator<Double> ede;
	private final ExprEvaluator<Boolean> ebe;
	private final ExprEvaluator<String> ese;
	
	public void run(StatementList sl) {
		List<Statement> statements = sl.getStatements();
		for(Statement s : statements) {
			if(!isOnHold()) {
				s.accept(this);
			}
			else {
				getProgram().appendNextStatement(s);
			}
		}
	}
	
	public void run(Fire fire) {
		getShip().fire();
		hold();
	}
	
	public void run(Thrust thrust) {
		getShip().setThruster(thrust.isActive());
		hold();
	}
	
	public void run(Turn turn) {
		double angle = turn.getExpr().accept(ede);
		getShip().rotate(angle);
		hold();
	}

	public void run(Skip skip) {
		// skip, do nothing
		hold();
	}
	
	public void run(If if1) {
		boolean CondResult = if1.getCondition().accept(ebe);
		if(CondResult==true) {
			if1.getThen().accept(this);
		} else {
			if1.getOtherwise().accept(this);
		}
	}

	public void run(While while1) {
		while(while1.getCondition().accept(ebe) && !isOnHold()) {
			while1.getBody().accept(this);
		}
		getProgram().appendNextStatement(while1);
	}
	
	public void run(Foreach foreach) {
		Set<? extends GameObject> objects = null;
		switch(foreach.getType()) {
		case ANY:
			objects = getShip().getWorld().getAllGameObjects();
			break;
		case ASTEROID:
			objects = getShip().getWorld().getAllAsteroids();
			break;
		case BULLET:
			objects = getShip().getWorld().getAllBullets();
			break;
		case SHIP:
			objects = getShip().getWorld().getAllShips();
			break;
		}
		for(GameObject go : objects) {
			EntityLiteral el = new EntityLiteral(foreach.getLine(), foreach.getColumn(), go);
			Assignment a = new Assignment(foreach.getLine(), foreach.getColumn(), foreach.getVariableName(), el);
			a.accept(this);
			foreach.getBody().accept(this);
			if(isOnHold()) {
				throw new IllegalArgumentException("Encountered an action statement in the ForEach loop!");		
			}
		}
	}

	public <T> void run(Assignment a) {
		try {
			String var = a.getVariable();
			Expression e = getProgram().getType(var).getLiteral(a.getRhs(), getShip());
			getProgram().putAssignment(var, e);
		}
		catch (IllegalArgumentException iae) {
			throw new IllegalArgumentException("Cannot assign incompatible types!", iae);
		}
		catch (NullPointerException npe) {
			throw new IllegalArgumentException("No such global variable!", npe);
		}
	}
	
	public void run(Print print) {
		String str = print.getExpr().accept(ese);
		System.out.println(str);
	}
	
	public boolean isOnHold() {
		return onHold;
	}
	
	public void release() {
		onHold = false;
	}
	
	public void hold() {
		onHold = true;
	}

	private boolean onHold;

}
